package com.travel.carmen;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
import android.speech.RecognizerIntent;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;


/**
   Launcher class for the Travel Mobile Application Carmen.
    The View is the Main Interface (layout as per main.xml) for the application.
    User navigation is propagated through this activity.
   	@author Arturo Aparicio 
   	@author Edwin Boykin 
   	@author Veena Chengalnaidu
   	@author Anton Ganeshalingam
   	@author Lakshmi Mallampati
   	@author Tarun Morton
   	@author Chantha Thoeun
 */
public class Main extends Activity implements OnInitListener
{
	// Identifiers
	
	// Constant Identifiers
	public static final int EMPTY = 0;
	public static final int INVALID = -1;
	public static final String BLANK = new String("");
	
	// Menu Identifiers
	public static final int MENU = Menu.FIRST;
	private static final int QUICK_NOTES_MID = 1;
	private static final int NOTES_MID = 2;
	private static final int LOGIN_MID = 3;
	private static final int LOGOUT_MID = 4;
	private static final int SAVE_MID = 5;
	private static final int DEL_MID = 6;
	private static final int ABOUT_MID = 7;
	private static final int RESET_MID = 8;
	
	// Speech Rate Identifier
	private static final float HALF = 0.5F;
	
	// Default Translate from Spinner position
	private static final int ENGLISH = 0;
	// Default Translate to Spinner position
	private static final int SPANISH = 1;
	
	// Request Identifiers
	private static final int VOICE_RECOGNITION_REQUEST_CODE = 1234;
	private static final int PSEUDO_CALL = 911;
	
	// Layout and TTS
	private Spinner translateFrom;
	private Spinner translateTo;
	private ArrayAdapter languages;
	private EditText fromEditText;
	private EditText toEditText;
	private Button recordButton;
	private Button translateButton;
	private Button speakButton;
	private Button swapButton;
	private TextToSpeech tts;
	
	// Member variables
	private boolean ttsInit = false;
	private boolean translation_error = false;
	private boolean signed_in = false;
	private boolean swapRequest = false;
	private ArrayList<String> matches; 
	private String username;
	private String password;
	private int currentNID = DrupalNode.INVALID_NID;
	private int currentCategoryIndex = Main.INVALID;
	private String currentTitle;
	
	
	@Override
	/**
	   Called when the activity is first created.
	    The UI activities as per main.xml are inflated.
	    All listeners and adapters are attached and all settings are
	    made to default.
	 */
	public void onCreate(Bundle bundle)
	{
		super.onCreate(bundle);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.main);

		//Drop-down boxes for language selection
		translateFrom = (Spinner)findViewById(R.id.translateFrom);
		translateTo = (Spinner)findViewById(R.id.translateTo);
		
		//Used to populate Spinners drop-down box
		languages = ArrayAdapter.createFromResource(this, 
				R.array.languages, android.R.layout.simple_spinner_item);
		languages.setDropDownViewResource(
				android.R.layout.simple_spinner_dropdown_item);
		
		//Set the list of languages to each drop down box
		translateFrom.setAdapter(languages);
		// Reset Screen upon changing source language
		translateFrom.setOnItemSelectedListener(translateFromItemSelected);
		translateTo.setAdapter(languages);
		// default from ENGLISH to SPANISH
		translateTo.setSelection(SPANISH);
		// Check if fromEditText has input already
		translateTo.setOnItemSelectedListener(translateToItemSelected);
		
		fromEditText = (EditText) findViewById(R.id.editText1);
		toEditText = (EditText) findViewById(R.id.editText2);
		recordButton = (Button) findViewById(R.id.record_button);
		translateButton = (Button) findViewById(R.id.translate_button);
		speakButton = (Button) findViewById(R.id.speakIT_button);
		swapButton = (Button) findViewById(R.id.swap_button);
		
		translateButton.setOnClickListener(translateListener);
		speakButton.setOnClickListener(speakListener);
		speakButton.setClickable(false);
		swapButton.setOnClickListener(swapListener);
		recordButton.setOnClickListener(recordListener);
		fromEditText.setOnKeyListener(textChangedListener);
		toEditText.setFocusable(false);
		
		// This starts up the TextToSpeech engine
    	// The first parameter is the context in which it is operating
    	// The second is the class which contains onInit        
        tts = new TextToSpeech(this, this);
	}
	
	
	/** 
	  KeyListener to reset translation on key press. 
	   Removes the translation (if any) on changes made to the 
	   corresponding Note.
	 */
	private OnKeyListener textChangedListener = new OnKeyListener() 
	{
		public boolean onKey(View v, int keyCode, KeyEvent event) 
		{
			// Printing, backspace & space might not be the only keys 
			// that need to be checked
			if (event.isPrintingKey() || keyCode == KeyEvent.KEYCODE_DEL
					|| keyCode == KeyEvent.KEYCODE_SPACE)
				toEditText.setText(BLANK);
			// doTranslation(); -- too complicated to implement 
			// translation on every key press since confusing
			// "Translation Unavailable" messages will be displayed
			// for no text, intermediate words (yet-to-be words), etc.
			return false;
		}
	};
	
	
	/** 
	   Language Spinner Listener to update translation (if any) 
	    corresponding to the Note upon changing the target Language
	 */
	private OnItemSelectedListener translateToItemSelected = new 
		OnItemSelectedListener() 
	{
		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
				long arg3) 
		{
			if (!fromEditText.getText().toString().equals(BLANK)
					&& !toEditText.getText().toString().equals(BLANK))
				doTranslation();
		}

		public void onNothingSelected(AdapterView<?> arg0) 
		{	/* do nothing */	}
	};
	
	
	/**
	   Language Spinner Listener to reset to default settings without
	    changing the Language Selections
	 */
	private OnItemSelectedListener translateFromItemSelected = new
		OnItemSelectedListener()
	{
		public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
				long arg3) 
		{
			// prevents reset on swap action
			if (!swapRequest)
				reset(false);
			// set flag off
			if (swapRequest)
				swapRequest = false;
		}
		
		public void onNothingSelected(AdapterView<?> arg0) 
		{	/* do nothing */	}
	};
	
	
	/** 
	 	Listener to swap Language selections. Also, to swap 
	     the Note with the corresponding Translation (if any) 
	 */
	private OnClickListener swapListener = new OnClickListener() 
	{
		public void onClick(View v) 
		{
			// set flag on
			swapRequest = true;
			
			// Swap Spinners
			int fromPosition = translateFrom.getSelectedItemPosition();
			int toPosition   = translateTo.getSelectedItemPosition();
			
			translateFrom.setSelection(toPosition);
			translateTo.setSelection(fromPosition);
			
			// Swap EditText
			String fromText = fromEditText.getText().toString();
			String toText   = toEditText.getText().toString();
			
			fromEditText.setText(toText);
			toEditText.setText(fromText);
			
			// To reinforce interchangeable translation compatibility
			if (!fromEditText.getText().toString().equals(BLANK))
				doTranslation();
		}
	};
	
	
	/** Listener to Translate a Note */
	private OnClickListener translateListener = new OnClickListener() 
	{
		public void onClick(View v) 
		{	
			if (fromEditText.getText().toString().length() > 0)
				doTranslation();	
			else // avoid unnecessary call to Google Translate
				Toast.makeText(Main.this, 
						"Please enter a Note", 1).show();
		}
	};
	
	
	/** 
	 	Text-to-Speech Listener for Carmen. If no Translation 
	 	 has been performed, the Listener will return to caller.
	 	 If Locale (identified by two letter lowercase codes as 
	 	 defined by ISO-639) for the Translated Language is available
	 	 on the running mobile (Android) OS, Text-to-Speech engine
	 	 locale is set and spoken in the corresponding language.
	 	 Else, a voice unsupported message is spoken in English
	 */
	private OnClickListener speakListener = new OnClickListener() 
	{
		public void onClick(View v) 
		{
			if(ttsInit) 
			{
	    		// Get the text from the text box
	        	String s = toEditText.getText().toString();
	        	// If "Translation" is blank
	        	if (toEditText.getText().toString().equals(EMPTY))
	        		return;	
	    	    // Get the native language voice (if any)
	        	Locale l = new Locale(getLanguageCode(
	        			translateTo.getSelectedItem().toString()));
	        	if(tts.isLanguageAvailable(l) == 
	        		TextToSpeech.LANG_COUNTRY_AVAILABLE
	        			|| tts.isLanguageAvailable(l) == 
	        				TextToSpeech.LANG_AVAILABLE)
	        	{
	        		tts.setLanguage(l);
	        		// Sets speech rate to half than normal
	        // tts.setSpeechRate(HALF);
	        		// This speaks the text
	        		tts.speak(s, TextToSpeech.QUEUE_FLUSH, null);
	        	}
	        	else
	        	{
	        		tts.setLanguage(Locale.US);//Locale.getDefault());
	        		// This speaks the text
	        		tts.speak("Translation unavailable in " 
	        				+ translateTo.getSelectedItem().toString(), 
	        				TextToSpeech.QUEUE_FLUSH, null);
	        	}
	    	}
		}
	};
	
	
	/** 
	 	Listener for Voice Recognition. If a microphone is detected (Device)
	   	 voice recording is set into motion. Else (Emulator), a microphone 
	   	 not detected error message is displayed and a simulation for results
	   	 is set into motion.
	 */
	private OnClickListener recordListener = new OnClickListener() 
	{
		public void onClick(View v) 
		{
			// Check to see if a recognition activity is present
	        PackageManager pm = getPackageManager();
	        List<ResolveInfo> activities = pm.queryIntentActivities(
	                new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
	        if (activities.size() != EMPTY) // VoiceRecognizer is present
	            recordVoice();
	        else 
	        {
	        	// Recognizer not present
	        	Toast.makeText(getApplicationContext(), 
						"Microphone not detected!", 1).show();
	        	emulatorRecordedSimulation();
	        }
		}
	};
	
	
	/** 
	   Simulates a recording for emulator testing purposes by providing 
		a list of Notes for the user to select.
	*/
	private void emulatorRecordedSimulation()
	{
		matches = new ArrayList<String>();
		matches.add("Hello");
		matches.add("Yellow");
		matches.add("Well Oh");
		onActivityResult(VOICE_RECOGNITION_REQUEST_CODE, PSEUDO_CALL, null);
	}
	
	
	/** Starts an activity for recording user speech */
	private void recordVoice()
	{
		Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Carmen is recording...");
        startActivityForResult(intent, VOICE_RECOGNITION_REQUEST_CODE);
	}
	
	/**
	   Makes a Google Translate call with the user set 
	    source and target language selections for the corresponding
	    user input Note.
	   If a translation is not available by Google Translate, the 
	    user is prompted with an appropriate message.
	 */
	private void doTranslation()
	{
		String textToTranslate = fromEditText.getText().toString();
		String fromLanguage    = getLanguageCode(
				translateFrom.getSelectedItem().toString());
		String toLanguage      = getLanguageCode(
				translateTo.getSelectedItem().toString());
		String result          = getTranslation(
				textToTranslate, fromLanguage, toLanguage);
		boolean error 		   = isValidTranslation();
		
		
		if (error)
		{
			Toast.makeText(getApplicationContext(), 
					"Translation unavailable", 1).show();
			return;
		}
		
		toEditText.setText(result);
	}

	
	/**
	   Retrieves and returns the translation from Google Translate for
	    the user input Note. If a translation error occurs, an Exception
	    is handled and a translation error flag is set.
	   @param textToTranslate Note to Translate
	   @param fromLanguage Source Language
	   @param toLanguage Target Language
	   @return a string representing the translated note in the target language
	 */
	private String getTranslation(String textToTranslate, String fromLanguage,
			String toLanguage) 
	{
		String translated = BLANK;
		try
		{
			translated = Translate.translate(
				textToTranslate, fromLanguage, toLanguage);
			translation_error = false;
		}
		catch (Exception e) 
		{
			translation_error = true;
		}
		return translated;
	}
	
	
	/** 
	 	Returns a Google translation error flag value
	    @return true if Google provided a translation without an error, 
	     false otherwise
	 */
	private boolean isValidTranslation()
	{
		return translation_error;
	}

	
	/** 
	    Get language two letter code as per Google Translate
	    /ISO_3166-1_alpha-2 Standard
	    @param fullFormLanguage Language spelled in English
	    @return two letter code ISO_3166-1_alpha-2 standard
	 */
	private String getLanguageCode(String fullFormLanguage) 
	{
		return Languages.getShortName(fullFormLanguage);
	}
	
	
	/**
	   Returns a dialog with a descriptive message about the Carmen 
	    Mobile Application purpose
	   @return Carmen About Dialog
	 */
	private AlertDialog about()
	{
		return 
		new AlertDialog.Builder(this)
		.setIcon(R.drawable.earth)
		.setTitle(getString(R.string.app_name) + "\n" + getString(R.string.version))
		.setMessage(getString(R.string.about_carmen) + "\n\n" 
				+ getString(R.string.carmen_copyright) + "\n" + getString(R.string.carmen_rights))
		.setPositiveButton("OK", new DialogInterface.OnClickListener() {
			@Override
			public void onClick(DialogInterface dialog, int which) 
			{ /* do nothing */ }
		})
		.create();
	}
	
	
	/**
	   Resets Carmen to default settings
	   @param resetLanguages if true, the language selections 
		are reset to default (ENGLISH to SPANISH), else the current
		user selections are left as is
	 */
	private void reset(boolean resetLanguages)
	{
		fromEditText.setText(BLANK);
		toEditText.setText(BLANK);
		currentTitle = null;
		// Reset NID to point to nothing
		currentNID = DrupalNode.INVALID_NID;
		currentCategoryIndex = Main.INVALID;
		if (resetLanguages)
		{
			translateFrom.setSelection(ENGLISH);
			translateTo.setSelection(SPANISH);
		}
	}

	
	/**
	  	Called on TTS Initialization
	  	@param status SUCCESS or ERROR
	 */
    public void onInit(int status) {
    	this.ttsInit = true;
    	speakButton.setClickable(true);
    }
    
    
    @Override
    /**
       Performs clean up (especially) for the text-to-speech engine
        before the activity is destroyed to prevent a crash
     */
	public void onDestroy() 
    {
    	super.onDestroy();
    	// This shuts down the TTS engine
    	tts.shutdown();
    }
    
    
    @Override
    /**
       Prepares the Menu Items
        Removes the Group and Dynamically recreates the items
        based on the app state.
     */
    public boolean onPrepareOptionsMenu(Menu menu) 
    {
    	menu.removeGroup(MENU);
    	menu.add(MENU, QUICK_NOTES_MID, 0, "Quick Notes");
        if (signed_in)
        {
        	menu.add(MENU, NOTES_MID, 0, "My Notes");
        	menu.add(MENU, SAVE_MID, 0, "Save");
        	menu.add(MENU, DEL_MID, 0, "Delete a Note");
        	menu.add(MENU, LOGOUT_MID, 0, "Logout");
        }
        else
        	menu.add(MENU, LOGIN_MID, 0, "Login");
        menu.add(MENU, ABOUT_MID, 0, "About");
        menu.add(MENU, RESET_MID, 0, "Reset");
        
    	return super.onPrepareOptionsMenu(menu);
    }

    
    @Override
    /**
       Listener for Menu Items selected
     */
    public boolean onOptionsItemSelected(MenuItem item) 
    {
    	switch (item.getItemId()) 
    	{
        	case QUICK_NOTES_MID:
	        	Intent qn = new Intent(this, QuickNotes.class);
	            startActivityForResult(qn, R.string.query_quick_note);
	            return true;
        	case NOTES_MID:
	        	Intent sn = new Intent(this, Categories.class);
	        	sn.putExtra(getString(R.string.caller), getString(R.string.query_saved_note));
	        	sn.putExtra(getString(R.string.username), username);
	        	sn.putExtra(getString(R.string.password), password);
	            startActivityForResult(sn, R.string.query_saved_note);
	            return true;
        	case DEL_MID:
        		Intent del = new Intent(this, Categories.class);
        		del.putExtra(getString(R.string.caller), getString(R.string.delete));
	        	del.putExtra(getString(R.string.username), username);
	        	del.putExtra(getString(R.string.password), password);
	            startActivityForResult(del, R.string.delete);
        		return true;
        	case LOGIN_MID:
	        	Intent login = new Intent(this, Login.class);
	            startActivityForResult(login, R.string.login);
	            return true;
        	case LOGOUT_MID:
	        	signed_in = false;
	            return true;
        	case SAVE_MID:
        		if (fromEditText.getText().toString().equals(BLANK))
        			Toast.makeText(getApplicationContext(), 
        					"Please Enter a Note", 1).show();
        		else
        		{
	        		Intent save = new Intent(this, Save.class);
	        		save.putExtra(getString(R.string.username), username);
		        	save.putExtra(getString(R.string.password), password);
		        	save.putExtra(getString(R.string.to_save_note), 
		        			fromEditText.getText().toString());
		        	save.putExtra(getString(R.string.trans_to_save_note), 
		        			toEditText.getText().toString());
		        	save.putExtra(getString(R.string.lang_src), 
		        			translateFrom.getSelectedItem().toString());
		        	save.putExtra(getString(R.string.lang_tar), 
		        			translateTo.getSelectedItem().toString());
		        	save.putExtra(DrupalNode.NID, currentNID);
		        	save.putExtra(getString(R.string.title), currentTitle);
		        	if (currentCategoryIndex != Main.INVALID)
		        		save.putExtra(getString(R.string.select_category), 
		        				currentCategoryIndex);
	        		startActivityForResult(save, R.string.save);
        		}
        		return true;
        	case ABOUT_MID:
	        	AlertDialog about = about();
	        	about.show();
	            return true;
        	case RESET_MID:
        		reset(true);
        		return true;
        }
        return super.onOptionsItemSelected(item);
    }
    
    
    @Override 
    /**
       Activity Callback responses. The actions performed are based on the
       requestCode and the resultCode of the activity returned from must
       be OK to proceed.
     */
    public void onActivityResult(int requestCode, int resultCode, Intent data) 
    {       
      switch(requestCode) 
      { 
        case R.string.query_quick_note : { 
          if (resultCode == RESULT_OK) 
          { 
        	  String result = data.getStringExtra(
        			  getString(R.string.query_quick_note));
        	  fromEditText.setText(result);
        	  toEditText.setText(BLANK);
          } 
          break; 
        } 
        case VOICE_RECOGNITION_REQUEST_CODE : {
        	if (resultCode == RESULT_OK)
        	{
        		// Fill the list view with best matches
                matches = data.getStringArrayListExtra(
                		RecognizerIntent.EXTRA_RESULTS);
                Intent voiceRecognized = new Intent(this, VoiceRecognized.class);
                voiceRecognized.putStringArrayListExtra(
                		getString(R.string.query_best_match), matches);
                startActivityForResult(voiceRecognized, R.string.query_best_match);
        	}
        	else if (resultCode == PSEUDO_CALL) // from emulator simulation
        	{
        		Intent voiceRecognized = new Intent(this, VoiceRecognized.class);
                voiceRecognized.putExtra(getString(R.string.query_best_match), matches);
                startActivityForResult(voiceRecognized, R.string.query_best_match);
        	}
        	break;
        }
        case R.string.query_best_match : 
        {
        	if (resultCode == RESULT_OK)
        	{
        		String result = data.getStringExtra(getString(R.string.query_best_match));
        		fromEditText.setText(result);
        		toEditText.setText(BLANK);
        	}
        	break;
        }
        case R.string.query_saved_note : 
        {
        	if (resultCode == RESULT_OK)
        	{
        		String result = data.getStringExtra(getString(R.string.query_saved_note));
        		currentTitle = data.getStringExtra(getString(R.string.title));
        		String translation = data.getStringExtra(getString(R.string.trans_to_save_note));
        		currentNID = data.getIntExtra(DrupalNode.NID, DrupalNode.INVALID_NID);
        		currentCategoryIndex = data.getIntExtra(getString(R.string.category_position), 
        				Main.INVALID);
        		String langSrc = data.getStringExtra(DrupalNode.LANGFROM);
        		String langTar  = data.getStringExtra(DrupalNode.LANGTO);
        		int lang_src_pos = Languages.getLanguageListPosition(langSrc);
        		int lang_tar_pos = Languages.getLanguageListPosition(langTar);
        		if (lang_src_pos != INVALID)
        			translateFrom.setSelection(lang_src_pos);
        		if (lang_tar_pos != INVALID)
        			translateTo.setSelection(lang_tar_pos);
        		fromEditText.setText(result);
        		toEditText.setText(translation);
        	}
        	break;
        }
        case R.string.login : 
        {
        	if (resultCode == RESULT_OK)
        	{
        		signed_in = true;
        		username = data.getStringExtra(getString(R.string.username));
        		password = data.getStringExtra(getString(R.string.password));
        		Toast.makeText(getApplicationContext(), 
    					"Welcome, " + username + "!", 1).show();
        	}
        	break;
        }
        case R.string.save:
        {
        	if (resultCode == RESULT_OK)
        	{
        		String note = data.getStringExtra(getString(R.string.to_save_note));
        		Toast.makeText(getApplicationContext(), "\"" + note + 
        				".\" This Note has been saved to your account!", 1).show();
        		reset(false);
        	}
        	break;
        }
        case R.string.delete:
        {
        	if (resultCode == RESULT_OK)
        	{
        		String deletedNote = data.getStringExtra(getString(R.string.delete));
        		Toast.makeText(getApplicationContext(), "\"" + deletedNote +
        				"\" has been deleted from your account!", 1).show();
        	}
        	break;
        }
        default:
        	super.onActivityResult(requestCode, resultCode, data);
      }
      Log.d("requestCode", "" + requestCode);
    }
}
